﻿using System.Collections.Generic;
using System.Xml.XPath;

namespace InfoPathHelpers
{
    public static class NodeHelpers
    {
        /// <summary>
        /// Checks if the supplied <paramref name="node"/> is null OR has no children AND no value.
        /// </summary>
        /// <param name="node">The node to check.</param>
        /// <returns>True if the node is null or empty; otherwise false.</returns>
        public static bool NodeIsNullOrEmpty(XPathNavigator node)
        {
            return node == null || string.IsNullOrEmpty(node.Value);
        }

        /// <summary>
        /// Checks if the supplied <paramref name="node"/> has any child elements (indicating it is a group).
        /// </summary>
        /// <param name="node">The node to check.</param>
        /// <returns>True if the has child elements; otherwise false.</returns>
        public static bool NodeIsGroup(XPathNavigator node)
        {
            return node.SelectChildren(XPathNodeType.Element).Count != 0;
        }

        public delegate void ProcessNodeDelegate(XPathNavigator node);

        /// <summary>
        /// Generic method which will execute the method provided by <paramref name="processNode"/> delegate
        /// for each element or attibute child of <paramref name="rootNode"/>
        /// </summary>
        /// <param name="rootNode">The parent node whos children are to be processed</param>
        /// <param name="nodesToIgnore">A list of local names of nodes that should not be processed.</param>
        /// <param name="groupsToIgnore">A list of local names of groups that should not be processed.</param>
        /// <param name="processNode">A delegate referencing the method which should be applied to each child node.</param>
        public static void ProcessAllNodes(XPathNavigator rootNode, List<string> nodesToIgnore, List<string> groupsToIgnore, ProcessNodeDelegate processNode)
        {
            if (nodesToIgnore == null)
            {
                nodesToIgnore = new List<string>();
            }

            if (groupsToIgnore == null)
            {
                groupsToIgnore = new List<string>();
            }

            // If the rootNode is null or has no children there is nothing to do.
            if (NodeIsNullOrEmpty(rootNode) || !rootNode.HasChildren)
            {
                return;
            }

            // We need to use the EndPosition to prevent 
            // moving past the end of the node being encrypted.
            XPathNavigator EndPosition = rootNode.Clone();
            while (!EndPosition.MoveToNext())
            {
                if (!EndPosition.MoveToParent())
                {
                    break;
                }
            }

            while (rootNode.MoveToFollowing(XPathNodeType.Element, EndPosition))
            {
                // While there are following elements.

                while (groupsToIgnore.Contains(rootNode.LocalName))
                {
                    // If the group is on the ignore list move to next sibling.
                    // Note: this is enclosed in a while so that if the next element is also 
                    // in the list of groups to ignore it is also skipped.
                    if (!rootNode.MoveToNext(XPathNodeType.Element))
                    {
                        // If there is no sibling to move to then we are done.
                        break;
                    }
                }

                if (nodesToIgnore.Contains(rootNode.LocalName))
                {
                    // Nodes with a local name matching one provided in the 
                    // nodesToIgnore list should be skipped.
                    continue;
                }


                if (rootNode.HasAttributes)
                {
                    // If the node has attributes move to the first one.
                    rootNode.MoveToFirstAttribute();
                    // Then loop through each one processing them.
                    do
                    {
                        if (rootNode.Prefix == "xsi" ||
                            nodesToIgnore.Contains(rootNode.LocalName) ||
                            NodeIsNullOrEmpty(rootNode))
                        {
                            // Nodes with a prefix "xsi" or a local name matching one provided in the 
                            // nodesToIgnore list should be skipped.
                            continue;
                        }
                        processNode(rootNode);
                    }
                    while (rootNode.MoveToNextAttribute());
                    // Finally return to the parent node.
                    rootNode.MoveToParent();
                }

                if (NodeIsGroup(rootNode) || NodeIsNullOrEmpty(rootNode))
                {
                    continue;
                }

                processNode(rootNode);
            }
        }
    }
}
